﻿
using GameServerApp.PVP.WorldMap;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace GameServerApp
{
    /// <summary>
    /// 客户端连接对象 负责和客户端通信
    /// </summary>
    public class ClientSocket
    {
        /// <summary>
        /// 所属角色(便于管理Socket)
        /// </summary>
        private Role m_Role;

        /// <summary>
        /// 客户端Socket
        /// </summary>
        private Socket m_socket;

        /// <summary>
        /// 接收数据的线程
        /// </summary>
        private Thread m_ReceiveThread;

        #region 接收消息所需变量
        /// <summary>
        /// 接收数据包的字节数组缓冲区
        /// </summary>
        private byte[] m_ReceiveBuffer = new byte[10240];

        /// <summary>
        /// 接收数据包的缓存数据流
        /// </summary>
        private MMO_MemoryStream m_ReceiveMS = new MMO_MemoryStream();

        /// <summary>
        /// 发送用的MS
        /// </summary>
        private MMO_MemoryStream m_SocketSendMS = new MMO_MemoryStream();

        /// <summary>
        /// 接收用的MS
        /// </summary>
        private MMO_MemoryStream m_SocketReciveMS = new MMO_MemoryStream();

        #endregion

        #region 发送消息所需变量
        /// <summary>
        /// 发送消息队列
        /// </summary>
        private Queue<byte[]> m_SendQueue = new Queue<byte[]>();

        /// <summary>
        /// 检查队列的委托
        /// </summary>
        private Action m_CheckSendQueue;

        /// <summary>
        /// 压缩数组的长度界限
        /// </summary>
        private const int m_ComperssLen = 200;
        #endregion

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="role"></param>
        public ClientSocket(Socket socket, Role role)
        {
            m_socket = socket;
            m_Role = role;
            m_Role.Client_Socket = this;

            //启动线程 进行接收数据
            m_ReceiveThread = new Thread(ReceiveMsg);
            m_ReceiveThread.Start();


            m_CheckSendQueue = OnCheckSendQueueCallBack;    //监听委托
            //temp  有客户端连接进来就回发一个数据
            //using (MMO_MemoryStream ms = new MMO_MemoryStream())
            //{
            //    ms.WriteUTF8String(string.Format("欢迎登陆服务器" + DateTime.Now.ToString()));
            //    this.SendMsg(ms.ToArray());
            //}
        }

        /// <summary>
        /// 接收数据
        /// </summary>
        private void ReceiveMsg()
        {
            //异步接收数据
            m_socket.BeginReceive(m_ReceiveBuffer, 0, m_ReceiveBuffer.Length, SocketFlags.None, RecceiveCallBack, m_socket);

        }

        /// <summary>
        /// 接收数据回调
        /// </summary>
        /// <param name="ar"></param>
        private void RecceiveCallBack(IAsyncResult ar)
        {
            try
            {
                int len = m_socket.EndReceive(ar);
                if (len > 0)
                {
                    //已经接收到数据

                    //把接收到数据 写入缓冲数据流的尾部
                    m_ReceiveMS.Position = m_ReceiveMS.Length;

                    //把指定长度的字节 写入数据流
                    m_ReceiveMS.Write(m_ReceiveBuffer, 0, len);

                    //如果换粗数据流的长度>2，说明至少有个不完整的包过来了
                    //为什么这里是2，因为我们客户端封装数据包 用的是ushort，长度就2
                    if (m_ReceiveMS.Length > 2)
                    {
                        while (true)
                        {
                            //把数据流指针位置放在0处
                            m_ReceiveMS.Position = 0;

                            //currMsglen = 包体的长度
                            int currMsgLen = m_ReceiveMS.ReadUShort();

                            //currFullMsg  总包的长度 = 包头的长度+包体的长度
                            int currFullMsgLen = 2 + currMsgLen;

                            //如果数据流的长度 >= 整包的长度  说明至少收到了一个完整包
                            if (m_ReceiveMS.Length >= currFullMsgLen)
                            {
                                //至少收到一个完整包

                                //定义包体的byte[]数组
                                byte[] buffer = new byte[currMsgLen];

                                //把数据流指针放到2的位置，也就是包体的位置
                                m_ReceiveMS.Position = 2;

                                //把包体读到byte[]数组
                                m_ReceiveMS.Read(buffer, 0, currMsgLen);

                                //==========开始解析数据 ====================
                                byte[] bufferNew = new byte[buffer.Length - 3];     //这是服务器异或后的数组

                                bool isCompress = false;
                                ushort crc = 0;

                                MMO_MemoryStream ms1 = this.m_ReceiveMS;
                                ms1.SetLength(0);
                                ms1.Write(buffer, 0, buffer.Length);
                                ms1.Position = 0;

                                isCompress = ms1.ReadBool();
                                crc = ms1.ReadUShort();
                                ms1.Read(bufferNew, 0, bufferNew.Length);


                                //1.CRC校验
                                int newCrc = Crc16.CalculateCrc16(bufferNew);
                                if (newCrc == crc)
                                {
                                    //2.异或 得到原始数据
                                    bufferNew = SecurityUtil.Xor(bufferNew);

                                    if (isCompress)
                                    {   //3.解压
                                        bufferNew = ZlibHelper.DeCompressBytes(bufferNew);
                                    }

                                    //协议编号
                                    ushort protoCode = 0;
                                    byte[] protoContent = new byte[buffer.Length - 2];

                                    MMO_MemoryStream ms2 = this.m_ReceiveMS;
                                    ms2.SetLength(0);
                                    ms2.Write(bufferNew, 0, bufferNew.Length);
                                    ms2.Position = 0;

                                    protoCode = ms2.ReadUShort();
                                    ms2.Read(protoContent, 0, protoContent.Length);


                                    //派发接收到的通讯协议到不同系统    protoContent 就是我们要的数据
                                    EventDispatcher.Instance.Dispatch(protoCode, m_Role, protoContent);     //派发通讯协议到监听的对象

                                }



                                //==========处理剩余字节数组 ====================

                                //剩余字节长度
                                int remainLen = (int)m_ReceiveMS.Length - currFullMsgLen;
                                if (remainLen > 0)
                                {
                                    //把指针放在第一个包的尾部
                                    m_ReceiveMS.Position = currFullMsgLen;

                                    //定义剩余字节数组
                                    byte[] remainBuffer = new byte[remainLen];

                                    //把数据流读到剩余字节数组
                                    m_ReceiveMS.Read(remainBuffer, 0, remainLen);

                                    //清空数据流
                                    m_ReceiveMS.Position = 0;
                                    m_ReceiveMS.SetLength(0);

                                    //把剩余字节数组重新写入数据流
                                    m_ReceiveMS.Write(remainBuffer, 0, remainBuffer.Length);

                                    remainBuffer = null;

                                    //待下一个循环开始
                                }
                                else
                                {
                                    //没有剩余字节
                                    //清空数据流
                                    m_ReceiveMS.Position = 0;
                                    m_ReceiveMS.SetLength(0);
                                    break;
                                }
                            }
                            else
                            {
                                //还没收到完整包
                                break;
                            }
                        }
                    }
                    //进行下一次接收数据包
                    ReceiveMsg();
                }
                else
                {
                    //客户端断开连接
                    Console.WriteLine("客户端{0}断开连接  {1}   ClientSocket,234", m_socket.RemoteEndPoint.ToString(), DateTime.Now);
                    m_Role.UpdateLastInWorldMap();  //更新客户端角色关游戏前最后位置
                    WorldMapSceneMgr.Instance.RoleLeave(m_Role.RoleId, m_Role.LastInWorldMapId);
                    //把角色添加到角色管理
                    RoleMgr.Instance.AllRole.Remove(m_Role);
                }
            }
            catch
            {
                //客户端断开连接(客户端异常退出，可能由于任务管理器直接结束造成)
                Console.WriteLine("客户端{0}断开连接    {1}   ClientSocket,243", m_socket.RemoteEndPoint.ToString(), DateTime.Now);
                m_Role.UpdateLastInWorldMap();  //更新客户端角色关游戏前最后位置
                WorldMapSceneMgr.Instance.RoleLeave(m_Role.RoleId, m_Role.LastInWorldMapId);
                //把角色添加到角色管理
                RoleMgr.Instance.AllRole.Remove(m_Role);
            }
        }

        //======================以下为发送消息==========================================================

        /// <summary>
        /// 检查队列的委托回调
        /// </summary>
        private void OnCheckSendQueueCallBack()
        {
            lock (m_SendQueue)
            {
                //如果队列中有数据包 则发送数据包
                if (m_SendQueue.Count > 0)
                {
                    //从队列中取出一个数据包，发送
                    Send(m_SendQueue.Dequeue());
                }
            }
        }

        /// <summary>
        /// 封装数据包（数据包格式：ushort包头 + byte[]包体)
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private byte[] MakeData(byte[] data)
        {
            byte[] retBuffer = null;

            //1.压缩标志  如果数据包的长度 大于了 m_ComperssLen 则进行压缩
            bool isCompress = data.Length > m_ComperssLen ? true : false;
            if (isCompress)
            {
                data = ZlibHelper.CompressBytes(data);
            }

            //2.异或
            data = SecurityUtil.Xor(data);

            //3.CRC检验
            ushort crc = Crc16.CalculateCrc16(data);    //CRC校验码

            //Console.WriteLine("Crc=" + crc);

            MMO_MemoryStream ms = m_SocketSendMS;
            ms.SetLength(0);

            ms.WriteUShort((ushort)(data.Length + 3));   //压缩标志1位,CRC校验码2位
            ms.WriteBool(isCompress);
            ms.WriteUShort(crc);
            ms.Write(data, 0, data.Length);
            retBuffer = ms.ToArray();

            return retBuffer;
        }

        /// <summary>
        /// 发送消息（不是真正发送消息，只是把消息加入队列）
        /// </summary>
        /// <param name="buffer"></param>
        public void SendMsg(byte[] buffer)
        {
            //得到封装后的数据包
            byte[] sendBuffer = MakeData(buffer);

            lock (m_SendQueue)   //锁一下，因为很多人/地方会调用
            {
                //把数据包加入到队列
                m_SendQueue.Enqueue(sendBuffer);

                //执行委托（启动）
                m_CheckSendQueue.BeginInvoke(null, null);
            }

        }

        /// <summary>
        /// 真正发送消息到服务器
        /// </summary>
        /// <param name="buffer"></param>
        private void Send(byte[] buffer)
        {
            m_socket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, SendCallBack, m_socket);
        }

        /// <summary>
        /// 发送数据包的回调
        /// </summary>
        /// <param name="ar"></param>
        private void SendCallBack(IAsyncResult ar)
        {
            m_socket.EndSend(ar);

            //继续检查队列
            OnCheckSendQueueCallBack();
        }
    }
}
